package shared.io.game;

import java.awt.Point;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import shared.Tiles;

/**
 * Used to calculate what rooms are currently visible.
 * Holds info about the maze.
 */
public class Maze2 implements Serializable {

   private static final long serialVersionUID = -4682058877785297266L;
   /**
    * List of rooms that have light in them (light > 0)
    */
   private List<Room> lightRooms;
   /**
    * Map of rooms
    */
   private Room[][] map;
   private List<Lightable> lights;
   private enum Direction {NORTH, EAST, SOUTH, WEST};
   
   public Maze2(Room[][] map, List<Lightable> lights) {
      this.map = map;
      this.lights = lights;
      lightRooms = new ArrayList<Room>();
   }
   
   /**
    * Width of the map
    */
   public int getWidth() {
      return map.length;
   }
   
   /**
    * Height of the map
    */
   public int getHeight() {
      return map[0].length;
   }
   
   /**
    * Gets a room on the map. If out of bounds returns null.
    */
   public Room getRoom(int x, int y) {
      try {
         return map[x][y];
      } catch (IndexOutOfBoundsException e) {
         return null;
      }
   }
  
   /**
    * Gets a list of currently visible rooms based on the list of players.
    */
   public List<Room> getVisibleRooms(List<Player> players, int torchDistance) {
      for (Room room : lightRooms) room.setLight(0);
      lightRooms = Collections.synchronizedList(new ArrayList<Room>());
      for (int x = 0; x < getWidth(); x++) {
         for (int y = 0; y < getHeight(); y++) {
            map[x][y].setLight(0);
         }
      }
      
      for (Player player : players) {
         getVisibleRooms(player, torchDistance);
      }
      for (Lightable light : lights) {
         getVisibleRooms(light, torchDistance);
      }
      return lightRooms;
   }
   
   private List<Room> getVisibleRooms(Lightable lightable, int torchDistance) {
      Room tempRoom;
      int i;
      increaseToLightLevel(lightable.getX(), lightable.getY(), torchDistance, torchDistance);
      for (i = 1; i <= torchDistance; i++) {
         //NORTH
         increaseToLightLevel(lightable.getX(), lightable.getY() - i, torchDistance - i + 1, torchDistance);
         tempRoom = (getRoom(lightable.getX(), lightable.getY() - i));
         if (tempRoom == null || tempRoom.getTile() == Tiles.WALL) break;
         increaseShadowRoomLight(getRoom(lightable.getX() + 1, lightable.getY() - i), torchDistance - i, torchDistance);
         increaseShadowRoomLight(getRoom(lightable.getX() - 1, lightable.getY() - i), torchDistance - i, torchDistance);
      }
      for (i = 1; i <= torchDistance; i++) {            
         //EAST
         increaseToLightLevel(lightable.getX() + i, lightable.getY(), torchDistance - i + 1, torchDistance);
         tempRoom = (getRoom(lightable.getX() + i, lightable.getY()));
         if (tempRoom == null || tempRoom.getTile() == Tiles.WALL) break;
         increaseShadowRoomLight(getRoom(lightable.getX() + i, lightable.getY() + 1), torchDistance - i, torchDistance);
         increaseShadowRoomLight(getRoom(lightable.getX() + i, lightable.getY() - 1), torchDistance - i, torchDistance);
      }
      for (i = 1; i <= torchDistance; i++) {
         //SOUTH
         increaseToLightLevel(lightable.getX(), lightable.getY() + i, torchDistance - i + 1, torchDistance);
         tempRoom = (getRoom(lightable.getX(), lightable.getY() + i));
         if (tempRoom == null || tempRoom.getTile() == Tiles.WALL) break;
         increaseShadowRoomLight(getRoom(lightable.getX() + 1, lightable.getY() + i), torchDistance - i, torchDistance);
         increaseShadowRoomLight(getRoom(lightable.getX() - 1, lightable.getY() + i), torchDistance - i, torchDistance);
      }
      for (i = 1; i <= torchDistance; i++) {
         //WEST
         increaseToLightLevel(lightable.getX() - i, lightable.getY(), torchDistance - i + 1, torchDistance);
         tempRoom = (getRoom(lightable.getX() - i, lightable.getY()));
         if (tempRoom == null || tempRoom.getTile() == Tiles.WALL) break;
         increaseShadowRoomLight(getRoom(lightable.getX() - i, lightable.getY() + 1), torchDistance - i, torchDistance);
         increaseShadowRoomLight(getRoom(lightable.getX() - i, lightable.getY() - 1), torchDistance - i, torchDistance);
      }
      return lightRooms;
   }
   
   public Room getRoom(Point loc) {
      return getRoom(loc.x, loc.y);
   }

   private Room getRoom(Direction dir, Room room) {
      if (dir == Direction.NORTH) {
         return getRoom(room.getX(), room.getY() - 1);
      } else if (dir == Direction.EAST) {
         return getRoom(room.getX() + 1, room.getY());
      } else if (dir == Direction.SOUTH) {
         return getRoom(room.getX(), room.getY() + 1);
      } else {
         return getRoom(room.getX() - 1, room.getY());
      }
   }
   
   /**
    * Increases the light level of the room to int level
    */
   private void increaseToLightLevel(int x, int y, int lightLevel, int torchDistance) {
      Room room = getRoom(x, y);
      if (room == null) return;
      room.increaseToLight((float)lightLevel / torchDistance);
      if (lightLevel > 0 && !lightRooms.contains(room)) lightRooms.add(room);
      return;
   }
   
   /**
    * Increases the light level of the room to int level
    */
   private void increaseToLightLevel(Room room, int lightLevel, int torchDistance) {
      if (room == null) return;
      room.increaseToLight((float)lightLevel / torchDistance);
      if (lightLevel > 0 && !lightRooms.contains(room)) lightRooms.add(room);
   }
   
   private void increaseShadowRoomLight(Room room, int lightLevel, int torchDistance) {
      if (lightLevel <= 0) return;
      if (room.getTile() == Tiles.WALL) {
         increaseToLightLevel(room, lightLevel, torchDistance);
      } else {
         increaseToLightLevel(room, 1, torchDistance);
      }
   }

   public List<Room> getAllRooms() {
      lightRooms.clear();
      for (int x = 0; x < getWidth(); x++) {
         for (int y = 0; y < getHeight(); y++) {
            Room room = map[x][y];
            room.setLight(1);
            lightRooms.add(room);
         }
      }
      return lightRooms;
   }
}
